Bitcoin Script Development Tutorial

2025-07-10

Bitcoin Script is a bit like the elephant in the room—everyone knows it exists, but few can see it clearly or care enough. This tutorial starts from the basics to help you understand how Bitcoin Script works and learn to write your own scripts. Since Bitcoin Script is not Turing-complete, the development process involves a lot of command-line operations and observing output.

1. Start a Local Node

Run this command to install the bitcoind binary, then test the installation with bitcoind --help:

brew install bitcoin

Create a directory for testing, such as bitcoin-regtest:

mkdir ./bitcoin-regtest
cd ./bitcoin-regtest

In this directory, create a bitcoin.conf file and add the following config:

regtest=1
txindex=1
fallbackfee=0.0001

This configuration sets up a local development node. The regtest=1 setting enables the local regression test network, starting from block height 0 and avoiding syncing with the public blockchain. txindex=1 enables transaction indexing for easier lookup, and fallbackfee sets the default transaction fee.

While in the directory with this config file, start the node:

bitcoind -datadir=./ -daemon

If successful, you’ll see “Bitcoin Core starting”. Check if the node is running:

bitcoin-cli -datadir=./ getblockchaininfo

To verify further, check the log:

cat ./regtest/debug.log

To stop the node:

bitcoin-cli -datadir=./ stop

Note: bitcoind starts the server, while bitcoin-cli is the client.

If you restart the node and find the wallet isn’t working, use this to load it:

bitcoin-cli -datadir=./ loadwallet learn-script 

2. Create a Wallet

Create a Bitcoin wallet:

bitcoin-cli -datadir=./ createwallet "learn-script"

This will create a folder under ./regtest/wallets named learn-script.

Generate a new wallet address:

bitcoin-cli -datadir=./ getnewaddress

My sample address: bcrt1q6c8d9vw62rdee72xcqx3d97w8qh8mfg8ky8zjw

Mine 101 blocks to this address:

bitcoin-cli -datadir=./ generatetoaddress 101 bcrt1q6c8d9vw62rdee72xcqx3d97w8qh8mfg8ky8zjw

Check the wallet balance (should be 50):

bitcoin-cli -datadir=./ getbalance

3. Send a Transaction

Generate a new receiving address:

bitcoin-cli -datadir=./ getnewaddress

Example: bcrt1qgq99zusgk3ekrzucs9uyqv5vpxnh66cjtwl6zc

Check the balance (should be 0):

bitcoin-cli -datadir=./ getreceivedbyaddress bcrt1qgq99zusgk3ekrzucs9uyqv5vpxnh66cjtwl6zc 0

Send 0.01 BTC to it:

bitcoin-cli -datadir=./ sendtoaddress bcrt1qgq99zusgk3ekrzucs9uyqv5vpxnh66cjtwl6zc 0.01

Get transaction details:

bitcoin-cli -datadir=./ gettransaction <txid>

Mine one block to confirm the transaction:

bitcoin-cli -datadir=./ generatetoaddress 1 bcrt1q6c8d9vw62rdee72xcqx3d97w8qh8mfg8ky8zjw

4. Inspect the Script

Use getrawtransaction and decoderawtransaction to view the transaction script:

bitcoin-cli -datadir=./ getrawtransaction <txid>
bitcoin-cli -datadir=./ decoderawtransaction <hex>

You’ll see txinwitness (signature and pubkey) and scriptPubKey (locking script). OP_0 in the script means an empty push (used in SegWit).

5. Debug with btcdeb

Use btcdeb to debug opcodes. Example:

btcdeb OP_0

And for a simple calculation:

btcdeb '[OP_2 OP_3 OP_ADD]'

Use step to execute each operation and watch the stack.

6. Write Bitcoin Script (1)

Write this simple script (insecure, for demo only):

[OP_2 OP_3 OP_ADD OP_5 OP_EQUAL]

Convert to hex: 5253935587

Generate P2SH address:

bitcoin-cli -datadir=./ decodescript 5253935587

Get descriptor:

bitcoin-cli -datadir=./ getdescriptorinfo "addr(<p2sh-address>)"

Create a watch-only wallet:

bitcoin-cli -datadir=./ createwallet "arith-watch" true true "" true

Import the script:

bitcoin-cli -datadir=./ -rpcwallet=arith-watch importdescriptors '[{"desc":"addr(<p2sh-address>)#<checksum>","timestamp":"now","label":"arith-2+3=5"}]'

Send BTC to the script address:

bitcoin-cli -datadir=./ -rpcwallet=learn-script sendtoaddress <p2sh-address> 0.01

Mine a block to confirm:

bitcoin-cli -datadir=./ generatetoaddress 1 <your-address>

7. Write Bitcoin Script (2)

Create a new address:

bitcoin-cli -datadir=./ -rpcwallet=learn-script getnewaddress

Use createrawtransaction, fundrawtransaction, signrawtransactionwithwallet, and sendrawtransaction to construct and broadcast a transaction spending from the script.

Mine another block to confirm:

bitcoin-cli -datadir=./ generatetoaddress 1 <your-address>

Check if the UTXO is spent:

bitcoin-cli -datadir=./ gettxout <txid> 0

8. Troubleshooting

Environment used:

OS: MacOS
bitcoind: v29.0.0
btcdeb: 5.0.24